﻿using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Specialized;

namespace System.Web
{
    /// <summary>
    /// Represents data to be posted to a URI.
    /// </summary>
    public class PostData
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PostData"/> class.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <param name="value">The value.</param>
        public PostData(string field, string value)
        {
            Field = field;
            Value = value;
        }

        /// <summary>
        /// Gets or sets the field.
        /// </summary>
        /// <value>The field.</value>
        public String Field { get; set; }

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public String Value { get; set; }
    }

    public static class UriExtensions
    {
        private class RequestAndData
        {
            public HttpWebRequest Request;
            public Byte[] Data;
        }

        private static void AddField(StringBuilder postString, PostData item)
        {
            Check.Require(postString, "postString");
            Check.Require(item, "item");
            Check.Require(item.Value, "item.Value");

            if (postString.Length == 0)
            {
                postString.AppendFormat("{0}={1}", item.Field, item.Value.Replace("&", "%26"));
            }
            else
            {
                postString.AppendFormat("&{0}={1}", item.Field, item.Value.Replace("&", "%26"));
            }
        }

        public static HttpWebRequest GetPostRequest(this Uri requestUri, IWebProxy proxy, out Byte[] data)
        {
            Check.Require(requestUri, "requestUri");
            Check.Require(proxy, "proxy");

            data = Encoding.ASCII.GetBytes(requestUri.AbsoluteUri);

            // Prepare web request...
            HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(requestUri);
            myRequest.Proxy = proxy;
            myRequest.Method = "POST";
            myRequest.ContentType = "application/x-www-form-urlencoded";
            myRequest.ContentLength = data.Length;

            return myRequest;
        }

        /// <summary>
        /// POSTs to the specified request URI string.
        /// </summary>
        /// <param name="requestUri">The request URI.</param>
        /// <param name="proxy">The proxy.</param>
        public static Byte[] Post(this Uri requestUri, IWebProxy proxy)
        {
            Check.Require(requestUri, "requestUri");
            Check.Require(proxy, "proxy");

            Byte[] data;
            HttpWebRequest myRequest = requestUri.GetPostRequest(proxy, out data);
            Stream postStream = myRequest.GetRequestStream();
            postStream.Write(data, 0, data.Length);

            WebResponse webResponse = myRequest.GetResponse();
            Stream responseStream = webResponse.GetResponseStream();
            data = new Byte[256];
            using (MemoryStream memoryStream = new MemoryStream())
            {
                Int32 read;
                do
                {
                    read = responseStream.Read(data, 0, 256);
                    memoryStream.Write(data, 0, read);
                } while (read > 0);

                data = memoryStream.ToArray();
            }

            return data;
        }

        private static void GetRequestStreamCallback(IAsyncResult asyncResult)
        {
            RequestAndData tmpRequestAndData = (RequestAndData)asyncResult.AsyncState;
            HttpWebRequest request = tmpRequestAndData.Request;
            Byte[] data = tmpRequestAndData.Data;
            // End the operation.

            Stream tmpPostStream = request.EndGetRequestStream(asyncResult);
            tmpPostStream.Write(data, 0, data.Length);
            tmpPostStream.Close();
        }

        private static HttpWebRequest GetPostRequest(String requestUriString, IEnumerable<PostData> postData, IWebProxy proxy, out Byte[] data)
        {
            Check.Require(requestUriString, "requestUriString", Check.NotNullOrEmpty);
            Check.Require(postData, "postData");
            Check.Require(proxy, "proxy");

            Uri url = GetFullUri(requestUriString, postData);
            return GetPostRequest(url, proxy, out data);
        }

        /// <summary>
        /// POSTs to the specified request URI string asynchronously.
        /// </summary>
        /// <param name="requestUriString">The request URI string.</param>
        /// <param name="postData">The post data.</param>
        /// <param name="proxy">The proxy.</param>
        public static void PostAsync(String requestUriString, IEnumerable<PostData> postData, IWebProxy proxy)
        {
            Check.Require(requestUriString, "requestUriString", Check.NotNullOrEmpty);
            Check.Require(postData, "postData");
            Check.Require(proxy, "proxy");

            Byte[] data;
            HttpWebRequest myRequest = GetPostRequest(requestUriString, postData, proxy, out data);

            RequestAndData tmpRequestAndData = new RequestAndData();
            tmpRequestAndData.Request = myRequest;
            tmpRequestAndData.Data = data;

            myRequest.BeginGetRequestStream(GetRequestStreamCallback, tmpRequestAndData);
        }

        /// <summary>
        /// Gets the full URI.
        /// </summary>
        /// <param name="requestUriString">The request URI string.</param>
        /// <param name="postData">The post data.</param>
        public static Uri GetFullUri(String requestUriString, IEnumerable<PostData> postData)
        {
            Check.Require(requestUriString, "requestUriString",Check.NotNullOrEmpty);
            Check.Require(postData, "postData");

            StringBuilder getString = new StringBuilder();
            foreach (PostData item in postData)
            {
                AddField(getString, item);
            }

            return new Uri(requestUriString + "?" + getString);
        }

        /// <summary>
        /// Replaces the host.
        /// </summary>
        /// <param name="uri">The URI in which to replace the host section.</param>
        /// <param name="newHost">The new host name.</param>
        /// <returns></returns>
        public static void ReplaceHost(this Uri uri, string newHost)
        {
            // Swap out the server in the URL
            uri = new Uri(string.Format("{0}://{1}{2}", uri.Scheme, newHost, uri.PathAndQuery));
        }
    }
}
